/*
Package paginator
@Author   : Uyynot
@Email    : uyynot@qq.com
@Time     : 2024/4/26 17:45
@File     : paginator.go
@Project  : gohub
@Desc     :
*/
package paginator

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/spf13/cast"
	"gohub/pkg/config"
	"gohub/pkg/logger"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"math"
	"strings"
)

// Paging
// @Description: 分页数据
type Paging struct {
	CurrentPage int    `json:"current_page"`  // 当前页码
	PerPage     int    `json:"per_page"`      // 每页显示数量
	TotalPage   int    `json:"total_page"`    // 总页数
	TotalCount  int64  `json:"total_count"`   // 总条数
	NextPageURL string `json:"next_page_url"` // 下一页链接地址
	PrevPageURL string `json:"prev_page_url"` // 上一页链接地址
}

// Paginator
// @Description: 分页操作类
type Paginator struct {
	BaseURL    string       // 基础链接地址，用以拼接url
	PerPage    int          // 每页显示数量
	Page       int          // 当前页码
	Offset     int          // 偏移量 数据库读取数据时 Offset的值
	TotalCount int64        // 总条数
	TotalPage  int          // 总页数 = TotalCount / PerPage
	Sort       string       // 排序字段
	Order      string       // 排序方式
	query      *gorm.DB     //db query句柄
	ctx        *gin.Context //gin上下文
}

// Paginate
// @Description: paginate分页
// @param c: gin.context 用来获取分页的 URL 参数
// @param db: GORM 查询句柄，用以查询数据集和获取数据总数
// @param data: 模型数组，传址获取数据
// @param baseURL: 用以分页链接
// @param perPage: 每页条数，优先从 url 参数里取，否则使用 perPage 的值
// @return Paging
// 用法:
//
//	   query := database.DB.Model(Topic{}).Where("category_id = ?", cid)
//	var topics []Topic
//	   paging := paginator.Paginate(
//	       c,
//	       query,
//	       &topics,
//	       app.APIURL(database.TableName(&Topic{})),
//	       perPage,
//	   )
func Paginate(c *gin.Context, db *gorm.DB, data any, baseURL string, perPage int) Paging {
	//初始化Paginator实例
	p := &Paginator{
		query: db,
		ctx:   c,
	}
	p.initProperties(perPage, baseURL)

	//查询数据库
	err := p.query.Preload(clause.Associations). //读取关联数据
							Order(p.Sort + " " + p.Order). //排序
							Limit(p.PerPage).              //每页显示数量
							Offset(p.Offset).              //偏移量
							Find(data).Error               //查询数据库

	//数据库出错
	if err != nil {
		logger.LogIf(err)
		return Paging{}
	}

	return Paging{
		CurrentPage: p.Page,
		PerPage:     p.PerPage,
		TotalPage:   p.TotalPage,
		TotalCount:  p.TotalCount,
		NextPageURL: p.getNextPageLink(),
		PrevPageURL: p.getPrevPageLink(),
	}

}

// initProperties
// @Description: 初始化分页必须用到的属性，基于这些属性查询数据库
// @receiver p
// @param perPage
// @param baseURL
func (p *Paginator) initProperties(perPage int, baseURL string) {
	p.BaseURL = p.formatBaseURL(baseURL)
	p.PerPage = p.getPerPage(perPage)

	//排序参数
	p.Order = p.ctx.DefaultQuery(config.Get("paging.url_query_order"), "asc")
	p.Sort = p.ctx.DefaultQuery(config.Get("paging.url_query_sort"), "id")

	p.TotalCount = p.getTotalCount()
	p.TotalPage = p.getTotalPage()
	p.Page = p.getCurrentPage()
	p.Offset = (p.Page - 1) * p.PerPage
}

// getPerPage
// @Description: 获取每页条数
// @receiver p
// @param perPage
// @return int
func (p *Paginator) getPerPage(perPage int) int {
	//优先使用用户传出的perPage参数
	queryPerPage := p.ctx.Query(config.Get("paging.url_query_per_page"))
	if len(queryPerPage) > 0 {
		perPage = cast.ToInt(queryPerPage)
	}
	//如果没有则使用默认值
	if perPage <= 0 {
		perPage = config.GetInt("paging.per_page")
	}
	return perPage
}

// getCurrentPage
// @Description: 返回当前页码
// @receiver p
// @return int
func (p *Paginator) getCurrentPage() int {
	//优先获取用户请求的页码
	page := cast.ToInt(p.ctx.Query(config.Get("paging.url_query_page")))
	//默认为1
	if page <= 0 {
		page = 1
	}
	//如果总页数等于0，则不够分页，直接返回0
	if p.TotalPage == 0 {
		return 0
	}

	//如果用户请求的页码大于总页数，则返回总页数，即最后一页
	if page > p.TotalPage {
		page = p.TotalPage
	}

	return page
}

// getTotalCount
// @Description: 返回数据库里的总条数
// @receiver p
// @return int64
func (p *Paginator) getTotalCount() int64 {
	var count int64
	if err := p.query.Count(&count).Error; err != nil {
		return 0
	}
	return count
}

// getTotalPage
// @Description: 获取总页数
// @receiver p
// @return int
func (p *Paginator) getTotalPage() int {
	if p.TotalCount == 0 {
		return 0
	}
	nums := int64(math.Ceil(float64(p.TotalCount) / float64(p.PerPage)))
	if nums == 0 {
		return 1
	}
	return int(nums)
}

// formatBaseURL
// @Description: 兼容url带与不带？的情况
// @receiver p
// @param baseURL
// @return string
func (p *Paginator) formatBaseURL(baseURL string) string {
	if strings.Contains(baseURL, "?") {
		baseURL += "&" + config.Get("paging.url_query_page") + "="
	} else {
		baseURL += "?" + config.Get("paging.url_query_page") + "="
	}
	return baseURL
}

// getPageLink
// @Description: 获取分页链接
// @receiver p
// @param page
// @return string
func (p *Paginator) getPageLink(page int) string {
	//拼接规则：BaseUrl+page+query_sort+query_order+query_per_page
	return fmt.Sprintf("%v%v&%s=%s&%s=%s&%s=%v",
		p.BaseURL,
		page,
		config.Get("paging.url_query_sort"),
		p.Sort,
		config.Get("paging.url_query_order"),
		p.Order,
		config.Get("paging.url_query_per_page"),
		p.PerPage,
	)
}

// getPrevPageLink
// @Description: 获取上一页链接地址
// @receiver p
// @return string
func (p *Paginator) getPrevPageLink() string {
	//如果当前页码大于1，且当前页码小于总页数，则返回上一页链接地址
	if p.Page > 1 && p.Page <= p.TotalPage {
		return p.getPageLink(p.Page - 1)
	}
	return ""
}

// getNextPageLink
// @Description: 获取下一页链接地址
// @receiver p
// @return string
func (p *Paginator) getNextPageLink() string {
	//如果当前页码小于总页数，则返回下一页链接地址
	if p.Page < p.TotalPage {
		return p.getPageLink(p.Page + 1)
	}
	return ""
}
